/*
 * Copyright (C) 2010-2011 Comcast Cable.  All Rights Reserved.
 */
package com.hercules.android.receiver.connection.parser.json.itemparsers;

import com.google.gjson.JSONException;
import com.google.gjson.JSONObject;
import com.hercules.android.receiver.connection.parser.CommandParsingException;
import com.hercules.android.receiver.connection.parser.MalformedCommandException;
import com.hercules.android.receiver.model.events.XREActivateViewEvent;
import com.hercules.android.receiver.model.events.XREAnimationEvent;
import com.hercules.android.receiver.model.events.XREApplicationExecExternalResultEvent;
import com.hercules.android.receiver.model.events.XREApplicationHTTPErrorEvent;
import com.hercules.android.receiver.model.events.XREApplicationHTTPResponseEvent;
import com.hercules.android.receiver.model.events.XREApplicationRPCCallEvent;
import com.hercules.android.receiver.model.events.XREApplicationRPCReturnEvent;
import com.hercules.android.receiver.model.events.XREApplicationReadLocalObjectEvent;
import com.hercules.android.receiver.model.events.XRECommandSequenceEvent;
import com.hercules.android.receiver.model.events.XREConnectEvent;
import com.hercules.android.receiver.model.events.XREErrorEvent;
import com.hercules.android.receiver.model.events.XREEvent;
import com.hercules.android.receiver.model.events.XREFlashEvent;
import com.hercules.android.receiver.model.events.XREFlashMetadataEvent;
import com.hercules.android.receiver.model.events.XREHTMLEvent;
import com.hercules.android.receiver.model.events.XREImageMetadataEvent;
import com.hercules.android.receiver.model.events.XREKeyboardEvent;
import com.hercules.android.receiver.model.events.XREMouseEvent;
import com.hercules.android.receiver.model.events.XREResourceEvent;
import com.hercules.android.receiver.model.events.XREResourceMeasuredEvent;
import com.hercules.android.receiver.model.events.XREShutdownEvent;
import com.hercules.android.receiver.model.events.XRETextInputEvent;
import com.hercules.android.receiver.model.events.XRETextMetadataEvent;
import com.hercules.android.receiver.model.events.XREVideoDynamicTransitionCompleteEvent;
import com.hercules.android.receiver.model.events.XREVideoMetadataEvent;
import com.hercules.android.receiver.model.events.XREStreamPlaybackStateEvent;
import com.hercules.android.receiver.model.events.XREVideoStatusEvent;
import com.hercules.android.receiver.model.events.XREVideoStreamProgressEvent;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * Parser for {@link XREEvent} properties.
 * <p>
 * AndroidAssembly2 this class was added in Android Assembly 2
 *
 * @author 7realm
 * @version 1.0
 */
public class EventParser extends FieldParser {
    /** Represents the map of event names to event classes. */
    private static final Map<String, Class<? extends XREEvent>> EVENTS = init();

    /**
     * Constructor for {@link EventParser}.
     *
     * @param required if property is required
     */
    public EventParser(boolean required) {
        super(required);
    }

    /**
     * Parses {@link XREEvent} property.
     *
     * @param path the path to value
     * @param json the JSON
     * @param keyName the name of current JSON key
     * @param instance the instance of object, parsed value will be set to it
     * @return the parsed value
     * @throws CommandParsingException if error occurs while parsing
     */
    @Override
    protected Object doParse(StringBuilder path, JSONObject json, String keyName, Object instance)
        throws CommandParsingException {
        try {
            JSONObject innerObject = json.get(keyName).isObject();

            // get required string property name
            String name =
                getStringValue(innerObject, "name", "XREEvent is missing required property 'name'. JSON " + json + ".");

            if (!EVENTS.containsKey(name)) {
                throw new MalformedCommandException("Unknown event name '" + name + "'. JSON '" + json + "'.");
            }

            // because object is expected to be plain POJO we can use newInstance here
            XREEvent event = EVENTS.get(name).newInstance();

            // parse map
            parseMap(path, innerObject, event, SetterType.FIELD);

            return event;
        } catch (IllegalAccessException e) {
            throw new CommandParsingException("Default constructor of command is not accessible. JSON '" + json + "'.",
                e);
        } catch (InstantiationException e) {
            throw new CommandParsingException("Failed to create command using default constructor. JSON '" + json
                + "'.", e);
        } catch (JSONException e) {
            throw new MalformedCommandException("Expected JSONObject at '" + path + "'. JSON '" + json + "'.", e);
        }
    }

    /**
     * Initializes all event names.
     *
     * @return the map of event names to event classes
     * @throws IllegalStateException if there is collision for event name
     */
    private static Map<String, Class<? extends XREEvent>> init() {
        List<Class<? extends XREEvent>> eventClasses = new ArrayList<Class<? extends XREEvent>>();
        eventClasses.add(XREActivateViewEvent.class);
        eventClasses.add(XREAnimationEvent.class);
        eventClasses.add(XREApplicationExecExternalResultEvent.class);
        eventClasses.add(XREApplicationHTTPErrorEvent.class);
        eventClasses.add(XREApplicationHTTPResponseEvent.class);
        eventClasses.add(XREApplicationReadLocalObjectEvent.class);
        eventClasses.add(XREApplicationRPCCallEvent.class);
        eventClasses.add(XREApplicationRPCReturnEvent.class);
        eventClasses.add(XRECommandSequenceEvent.class);
        eventClasses.add(XREConnectEvent.class);
        eventClasses.add(XREErrorEvent.class);
        eventClasses.add(XREFlashEvent.class);
        eventClasses.add(XREFlashMetadataEvent.class);
        eventClasses.add(XREHTMLEvent.class);
        eventClasses.add(XREImageMetadataEvent.class);
        eventClasses.add(XREKeyboardEvent.class);
        eventClasses.add(XREMouseEvent.class);
        eventClasses.add(XREResourceEvent.class);
        eventClasses.add(XREResourceMeasuredEvent.class);
        eventClasses.add(XREShutdownEvent.class);
        eventClasses.add(XRETextInputEvent.class);
        eventClasses.add(XRETextMetadataEvent.class);
        eventClasses.add(XREVideoDynamicTransitionCompleteEvent.class);
        eventClasses.add(XREVideoMetadataEvent.class);
        eventClasses.add(XREStreamPlaybackStateEvent.class);
        eventClasses.add(XREVideoStatusEvent.class);
        eventClasses.add(XREVideoStreamProgressEvent.class);

        // look for all event names
        Map<String, Class<? extends XREEvent>> map = new HashMap<String, Class<? extends XREEvent>>();
        for (Class<? extends XREEvent> eventClass : eventClasses) {
            for (Field field : eventClass.getDeclaredFields()) {
                if (field.getName().endsWith("_EVENT_NAME")) {
                    try {
                        String eventName = field.get(null).toString();
                        // AndroidAssembly3 added event name collision checking
                        if (map.containsKey(eventName)) {
                            throw new IllegalStateException("Event with name '" + eventName + "' is present both in '"
                                + eventClass.getName() + "' and in '" + map.get(eventName).getName() + "'.");
                        }
                        map.put(eventName, eventClass);
                    } catch (IllegalArgumentException e) {
                        // ignore
                    } catch (IllegalAccessException e) {
                        // ignore
                    }
                }
            }
        }
        return map;
    }
}
